package org.nebula.contrib.ngbatis.proxy;


import com.vesoft.nebula.client.graph.data.ResultSet;
import org.nebula.contrib.ngbatis.exception.QueryException;
import org.nebula.contrib.ngbatis.models.ClassModel;
import org.nebula.contrib.ngbatis.models.MethodModel;
import org.nebula.contrib.ngbatis.utils.Page;
import org.springframework.data.repository.query.Param;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static org.nebula.contrib.ngbatis.proxy.NebulaDaoBasicExt.*;

/**
 * <p>
 * 注意:
 * 可以新增方法, 尽量不要修改原有方法的参数列表
 * 可以修改 UdfNebulaDaoBasic.xml
 * </p>
 *
 * <p>
 * jar包外代码的优先级高于maven依赖jar包的优先级
 * 以用于替换 ngbatis jar包里的数据访问NebulaDaoBasic基类,
 * 在class加载时以实现自定义公共逻辑
 * 结合UdfNebulaDaoBasic.xml使用
 * </p>
 *
 * <dependency>
 * <groupId>org.nebula-contrib</groupId>
 * <artifactId>ngbatis</artifactId>
 * <version>${nebula.version}</version>
 * </dependency>
 *
 * @author gin
 */
public interface NebulaDaoBasic<T, I extends Serializable> {

    // region query zoom

    /**
     * <p>通过主键查询对应表的单条记录</p>
     *
     * @param id 记录主键
     * @return 表中的记录对应的实体对象
     */
    default T selectById(@Param("id") I id) {
        MethodModel methodModel = getMethodModel();
        return (T) MapperProxy.invoke(methodModel, id);
    }

    /**
     * <p>通过多个 id 值查询符合条件的记录</p>
     *
     * @param ids 多个 id
     * @return 多个 id 对应的节点
     */
    default List<T> selectByIds(@Param("ids") Collection<I> ids) {
        MethodModel methodModel = getMethodModel();
        Class<?> currentType = this.getClass();
        Class<?> entityType = entityType(currentType);
        methodModel.setResultType(entityType);
        ClassModel classModel = new ClassModel();
        classModel.setNamespace(currentType);
        return (List<T>) MapperProxy.invoke(classModel, methodModel, ids);
    }

    /**
     * <p>以实体类为载体，存放查询条件，不为空的属性为查询条件</p>
     *
     * @param record 单个节点做为查询条件
     * @return 符合条件节点的集合
     */
    default List<T> selectBySelective(T record) {
        MethodModel methodModel = getMethodModel();
        methodModel.setReturnType(List.class);
        methodModel.setResultType(entityType(this.getClass()));
        return (List<T>) MapperProxy.invoke(methodModel, record);
    }

    /**
     * <p>以实体类为载体，存放查询条件，不为空的属性为查询条件，String 类型的属性使用模糊查询</p>
     *
     * @param record 查询条件
     * @return 符合条件的节点集合
     */
    default List<T> selectBySelectiveStringLike(T record) {
        MethodModel methodModel = getMethodModel();
        methodModel.setReturnType(List.class);
        methodModel.setResultType(entityType(this.getClass()));
        return (List<T>) MapperProxy.invoke(methodModel, record);
    }

    /**
     * <p>按条件查出所有符合条件的记录的 主键 </p>
     *
     * @param record 查询条件
     * @return 符合查询条件的节点 id
     */
    default List<I> selectIdBySelective(T record) {
        MethodModel methodModel = getMethodModel();
        methodModel.setResultType(pkType(this.getClass()));
        return (List<I>) MapperProxy.invoke(methodModel, record);
    }

    /**
     * <p>按条件查出所有符合条件的记录的 主键 （String字段模糊查询）</p>
     *
     * @param record 查询条件
     * @return 符合查询条件的节点 id
     */
    default List<I> selectIdBySelectiveStringLike(T record) {
        MethodModel methodModel = getMethodModel();
        methodModel.setResultType(pkType(this.getClass()));
        return (List<I>) MapperProxy.invoke(methodModel, record);
    }

    /**
     * <p>通过 map 存放查询参数，查询多条记录并映射成实体类</p>
     *
     * @param param 查询条件
     * @return 符合查询条件的节点集合
     */
    default List<T> selectByMap(Map<String, Object> param) {
        MethodModel methodModel = getMethodModel();
        methodModel.setReturnType(List.class);
        methodModel.setResultType(entityType(this.getClass()));
        ClassModel classModel = new ClassModel();
        classModel.setNamespace(this.getClass());
        return (List<T>) MapperProxy.invoke(classModel, methodModel, param);
    }

    /**
     * <p>统计符合条件的记录数</p>
     *
     * @param param 查询条件
     * @return 统及符合查询条件的总节点数
     */
    default Long countByMap(Map<String, Object> param) {
        MethodModel methodModel = getMethodModel();
        ClassModel classModel = new ClassModel();
        classModel.setNamespace(this.getClass());
        return (Long) MapperProxy.invoke(classModel, methodModel, param);
    }

    /**
     * 查询对应类型的数据并分页
     *
     * @param page 分页的参数，与分页结果的容器
     * @return 分页的结果
     */
    default List<T> selectPage(Page<T> page) {
        MethodModel methodModel = getMethodModel();
        Long total = countPage(page);
        page.setTotal(total);
        if (total == 0) {
            return Collections.EMPTY_LIST;
        }
        methodModel.setReturnType(List.class);
        methodModel.setResultType(entityType(this.getClass()));
        List<T> proxy = (List<T>) MapperProxy.invoke(methodModel, page);
        page.setRows(proxy);
        return proxy;
    }

    default Long countPage(Page<T> page) {
        MethodModel methodModel = getMethodModel();
        return (Long) MapperProxy.invoke(methodModel, page);
    }
    // endregion

    // region insert zoom

    /**
     * <p>插入一条记录，全属性插入</p>
     *
     * @param record 当前表对应的记录数据
     * @return 是否删除成功，成功 1，失败 0
     */
    default Integer insert(T record) {
        MethodModel methodModel = getMethodModel();
        methodModel.setReturnType(ResultSet.class);
        methodModel.setResultType(ResultSet.class);
        ResultSet resultSet = (ResultSet) MapperProxy.invoke(methodModel, record);
        return resultSet.isSucceeded() ? 1 : 0;
    }

    /**
     * <p>插入非空字段。</p>
     *
     * @param record 单个顶点
     * @return 是否删除成功，成功 1，失败 0
     */
    default Integer insertSelective(T record) {
        MethodModel methodModel = getMethodModel();
        methodModel.setReturnType(ResultSet.class);
        methodModel.setResultType(ResultSet.class);
        ResultSet resultSet = (ResultSet) MapperProxy.invoke(methodModel, record);
        return resultSet.isSucceeded() ? 1 : 0;
    }

    /**
     * 批量插入全字段
     *
     * @param ts 当前Tag下的多节点
     */
    default void insertBatch(List<T> ts) {
        MethodModel methodModel = getMethodModel();
        MapperProxy.invoke(methodModel, ts);
    }
    // endregion

    // region update zoom
    default T updateById(T record) {
        MethodModel methodModel = getMethodModel();
        Class<?> entityType = record.getClass();
        methodModel.setReturnType(entityType);
        methodModel.setResultType(entityType);
        return (T) MapperProxy.invoke(methodModel, record);
    }

    /**
     * <p>更新</p>
     *
     * @param record 节点
     * @return 是否删除成功，成功 1，失败 0
     */
    default T updateByIdSelective(T record) {
        MethodModel methodModel = getMethodModel();
        Class<?> entityType = record.getClass();
        methodModel.setReturnType(entityType);
        methodModel.setResultType(entityType);
        return (T) MapperProxy.invoke(methodModel, record);
    }

    /**
     * 批量更新行记录，selective
     *
     * @param ts 当前Tag下的多节点
     */
    default void updateByIdBatchSelective(List<T> ts) {
        MethodModel methodModel = getMethodModel();
        MapperProxy.invoke(methodModel, ts);
    }
    // endregion

    // region delete zoom

    /**
     * <p>数据操作，逻辑删除接口，前提当前类 有字段 is_del </p>
     *
     * @param id 表记录主键
     * @return 是否执行成功，成功 1 ，失败 0
     */
    default int deleteLogicById(I id) {
        throw new QueryException("No implements");
    }

    /**
     * <p>数据操作，根据节点 id 将节点连同其连接的关系一同物理删除</p>
     *
     * @param id 表记录主键
     * @return 是否执行成功，成功 1 ，失败 0
     */
    default int deleteWithEdgeById(I id) {
        MethodModel methodModel = getMethodModel();
        methodModel.setReturnType(ResultSet.class);
        methodModel.setResultType(ResultSet.class);
        ResultSet resultSet = (ResultSet) MapperProxy.invoke(methodModel, id);
        return resultSet.isSucceeded() ? 1 : 0;
    }

    /**
     * <p>通过 主键删除当前记录</p>
     *
     * @param id 表记录主键
     * @return 是否删除成功，成功 1，失败 0
     */
    default int deleteById(I id) {
        MethodModel methodModel = getMethodModel();
        methodModel.setReturnType(ResultSet.class);
        methodModel.setResultType(ResultSet.class);
        ResultSet resultSet = (ResultSet) MapperProxy.invoke(methodModel, id);
        return resultSet.isSucceeded() ? 1 : 0;
    }
    // endregion

    // region graph special

    /**
     * 根据三元组值，插入关系
     *
     * @param v1 开始节点值
     * @param e  关系值
     * @param v2 结束节点值
     */
    default void insertEdge(Object v1, Object e, Object v2) {
        if (v2 == null || v1 == null || e == null) {
            return;
        }
        MethodModel methodModel = getMethodModel();
        MapperProxy.invoke(methodModel, v1, e, v2);
    }

    /**
     * 提供开始节点的id、结束节点的id 与 关系名，判断是否已经建立关系
     *
     * @param startId  开始节点的 id
     * @param edgeType 关系类型
     * @param endId    结束节点的 id
     * @return 数据库中，两个 id 的节点是否有关系
     */
    default Boolean existsEdge(I startId, Class<?> edgeType, I endId) {
        String cqlTpl = getCqlTpl();
        String edgeName = edgeName(edgeType);
        return (Boolean) proxy(this.getClass(), Boolean.class, cqlTpl,
                new Class[]{Serializable.class, Class.class, Serializable.class}, startId, edgeName,
                endId);
    }

    /**
     * 通过结束节点id与关系类型获取所有开始节点，<br> 开始节点类型为当前接口实现类所管理的实体对应的类型
     *
     * @param edgeType 关系类型
     * @param endId    结束节点的 id
     * @return 开始节点
     */
    default List<T> listStartNodes(Class<?> edgeType, I endId) {
        Class<?> startType = entityType(this.getClass());
        return (List<T>) listStartNodes(startType, edgeType, endId);
    }

    /**
     * 指定开始节点类型，并通过结束节点id与关系类型获取所有开始节点
     *
     * @param startType 开始节点的类型
     * @param edgeType  关系类型
     * @param endId     结束节点的 id
     * @return 开始节点
     */
    default List<?> listStartNodes(Class<?> startType, Class<?> edgeType, I endId) {
        String cqlTpl = getCqlTpl();
        String startVertexName = vertexName(startType);
        String edgeName = edgeName(edgeType);
        Class<? extends NebulaDaoBasic> daoType = this.getClass();
        Class<?> returnType = entityType(daoType);
        return (List<?>) proxy(daoType, returnType, cqlTpl,
                new Class[]{Class.class, Class.class, Serializable.class}, startVertexName, edgeName,
                endId);
    }

    /**
     * 通过结束节点id与关系类型获取第一个开始节点，<br> 开始节点类型为当前接口实现类所管理的实体对应的类型 （对应类型）
     *
     * @param edgeType 关系类型
     * @param endId    结束节点的 id
     * @return 开始节点
     */
    default T startNode(Class<?> edgeType, I endId) {
        Class<?> startType = entityType(this.getClass());
        return (T) startNode(startType, edgeType, endId);
    }

    /**
     * 指定开始节点类型，并通过结束节点id与关系类型获取第一个开始节点
     *
     * @param startType 开始节点的类型
     * @param edgeType  关系类型
     * @param endId     结束节点的 id
     * @param <E>       开始节点的类型
     * @return 开始节点
     */
    default <E> E startNode(Class<E> startType, Class<?> edgeType, I endId) {
        String cqlTpl = getCqlTpl();
        String startVertexName = vertexName(startType);
        String edgeName = edgeName(edgeType);
        Class<? extends NebulaDaoBasic> daoType = this.getClass();
        Class<?> returnType = entityType(daoType);
        return (E) proxy(daoType, returnType, cqlTpl,
                new Class[]{Class.class, Class.class, Serializable.class}, startVertexName, edgeName,
                endId);
    }
    // endregion


    // 自定义模块 udf region
    /**
     * <p>新增/更新</p>
     * <p>Selective: 仅处理非空字段</p>
     *
     * @param record 节点
     */
    default void upsertByIdSelective(T record) {
        MethodModel methodModel = getMethodModel();
        Class<?> entityType = record.getClass();
        methodModel.setReturnType(entityType);
        methodModel.setResultType(entityType);
        MapperProxy.invoke(methodModel, record);
    }

    /**
     * 根据三元组值, 插入关系
     * <p>Selective: 仅处理非空字段</p>
     *
     * @param src 开始节点值
     * @param edge  关系值
     * @param dst 结束节点值
     */
    default void insertEdgeSelective(Object src, Object edge, Object dst) {
        if (dst == null || src == null || edge == null) {
            return;
        }
        MethodModel methodModel = getMethodModel();
        MapperProxy.invoke(methodModel, src, edge, dst);
    }

    /**
     * 根据三元组值, 插入关系
     * <p>Selective: 仅处理非空字段</p>
     *
     * @param src 开始节点值
     * @param edge  关系值
     * @param dst 结束节点值
     */
    default void upsertEdgeSelective(Object src, Object edge, Object dst) {
        if (dst == null || src == null || edge == null) {
            return;
        }
        MethodModel methodModel = getMethodModel();
        MapperProxy.invoke(methodModel, src, edge, dst);
    }

    // 自定义模块 end udf region


}



